package com.meego.common.timer;

import com.meego.common.log.GA;
import com.meego.common.misc.SafeListIterator;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class EventTimer extends MonitoredThread {

    private static EventTimer sInstance;
    private boolean mShutdown;
    private List<TimedEventMonitor> mEvents;
    private long mNextWakeUp;
    private boolean mChanged;
    private static int MAX_CHUNK_COUNT = 50;
    private static int MAX_CYCLE_COUNT = 100000;

    public static EventTimer getInstance() {
        if (sInstance == null) {
            newInstance();
        }

        return sInstance;
    }

    public static void setInstance(EventTimer aTimer) {
        sInstance = aTimer;
    }

    private static synchronized void newInstance() {
        if (sInstance == null) {
            sInstance = new EventTimer();
        }
    }

    protected EventTimer() {
        super("Event Timer");

        this.mShutdown = false;
        this.mEvents = new LinkedList<TimedEventMonitor>();
        this.mNextWakeUp = 0L;

        start();
    }

    public void addTimedEvent(String aName, TimedEvent anEvent) {
        addTimedEvent(aName, anEvent, true);
    }

    public synchronized void addTimedEvent(String aName, TimedEvent anEvent, boolean ignorePastEvents) {
        GA.timer.info("try to add TimedEvent");
        for (TimedEventMonitor tem : this.mEvents) {
            if (tem.mTimedEvent == anEvent) {
                GA.timer.error("TimedEvent already registered - " + aName);
                return;
            }
        }

        TimedEventMonitor tem = new TimedEventMonitor();
        tem.mName = aName;
        tem.mIgnorePastEvents = ignorePastEvents;
        tem.mTimedEvent = anEvent;
        tem.mNextEvent = 0L;

        calcNextEvent(System.currentTimeMillis(), tem);
        if (tem.mNextEvent == 0L) {
            GA.timer.debug("TimedEvent added - " + tem.mName);
            return;
        }

        this.mEvents.add(tem);

        GA.timer.debug("TimedEvent added - " + tem.mName);

        if (((tem.mNextEvent < this.mNextWakeUp) || (this.mNextWakeUp == 0L)) && (!this.mChanged)) {
            this.mChanged = true;
            notifyAll();
        }
    }

    private void calcNextEvent(long now, TimedEventMonitor tem) {
        long cycleInPast = -1L;
        int cycleCount = 0;
        boolean cycleFinish = false;

        while ((!cycleFinish) && (cycleCount < MAX_CYCLE_COUNT)) {
            long inPast = -1L;

            for (int chk = 0; chk < MAX_CHUNK_COUNT; chk++) {
                inPast = -1L;
                Date date = null;
                try {
                    date = tem.mTimedEvent.getNextEvent();
                } catch (RuntimeException ex) {
                    GA.timer.error(tem.mName + ".getNextEvent threw " + ex.getMessage(), ex);
                }

                if (date == null) {
                    tem.mNextEvent = 0L;
                    inPast = 0L;
                    cycleFinish = true;
                    break;
                }
                tem.mNextEvent = date.getTime();

                if (now > tem.mNextEvent) {
                    inPast = now - tem.mNextEvent;
                    GA.timer.warn("Next event for " + tem.mName + " is in the past:" + inPast / 1000L + " seconds");
                }

                if (!tem.mIgnorePastEvents) {
                    GA.timer.debug("calcNextEvent - not ignoring past events. this one next on timer list [" + tem.mName + "]  time " + tem.mNextEvent);
                    cycleFinish = true;
                    break;
                }
                if (inPast > 0L) {
                    GA.timer.debug("Ignoring past event for " + tem.mName + " overdue " + inPast / 1000L + " seconds");
                }

                if (inPast < 0L) {
                    cycleFinish = true;
                    break;
                }

                tem.mNextEvent = 0L;
            }

            if (inPast > 0L) {
                if ((cycleInPast > 0L) && (inPast >= cycleInPast)) {
                    GA.timer.debug("calcNextEvent - previous inPast ("
                            + cycleInPast + ") <= inPast (" + inPast
                            + "). Stopping timed event search after "
                            + (cycleCount + 1) + " cycles");
                    break;
                }
                GA.timer.debug("calcNextEvent - previous inPast ("
                        + cycleInPast + ") > inPast (" + inPast
                        + "). Repeating search for current timed event after "
                        + cycleCount + " cycles");
                cycleInPast = inPast;
            } else {
                if (inPast <= 0L) {
                    break;
                }
            }
            cycleCount++;
        }

        if (cycleCount >= MAX_CYCLE_COUNT) {
            GA.timer.warn("Exceeded max cycle count [" + cycleCount
                    + "] looking for current event [" + tem.mName + "]");
        }

        if (tem.mNextEvent > 0L) {
            long l_msec = tem.mNextEvent - now;

            int sec = (int) (l_msec / 1000L);
            int min = sec / 60;
            int hour = min / 60;
            int msec = (int) (l_msec % 1000L);

            sec %= 60;
            min %= 60;

            StringBuilder result = new StringBuilder();
            result.append(tem.mName);
            result.append(" in ");
            result.append(hour);
            result.append(':');
            result.append(min);
            result.append(':');
            result.append(sec);
            result.append('.');
            result.append(msec);
            GA.timer.debug(result.toString());
        }
    }

    public synchronized int getEventCount() {
        GA.timer.debug("mEvents:" + this.mEvents);
        return this.mEvents.size();
    }

    public synchronized void removeTimedEvent(TimedEvent anEvent) {
        if (anEvent == null) {
            return;
        }

        for (Iterator scan = this.mEvents.iterator(); scan.hasNext();) {
            TimedEventMonitor tem = (TimedEventMonitor) scan.next();
            if (tem.mTimedEvent == anEvent) {
                GA.timer.debug("TimedEvent removed - " + tem.mName);
                scan.remove();
            }
        }
    }

    @Override
    public void run() {
        GA.timer.info("Event Timer Started");
        try {
            while (!this.mShutdown) {
                GA.timer.debug("Scanning TimedEvent list");

                setWaitMode(false);

                long now = System.currentTimeMillis();
                Iterator scan;
                synchronized (this) {
                    scan = new SafeListIterator(this.mEvents);
                }

                while (scan.hasNext()) {
                    TimedEventMonitor tem = (TimedEventMonitor) scan.next();

                    boolean runEvent = false;
                    boolean getNextEvent = false;

                    if (tem.mNextEvent == 0L) {
                        getNextEvent = true;
                    } else if (now >= tem.mNextEvent) {
                        runEvent = true;
                        getNextEvent = true;
                    }

                    if (runEvent) {
                        GA.timer.info("running - " + tem.mName);
                        try {
                            tem.mTimedEvent.doTimedEvent();
                        } catch (RuntimeException ex) {
                            GA.timer.error(tem.mName + ".doTimedEvent threw runtime exception ", ex);
                        }
                    }

                    if (getNextEvent) {
                        calcNextEvent(now, tem);

                        if (tem.mNextEvent == 0L) {
                            GA.timer.debug("TimedEvent removed - " + tem.mName);
                            synchronized (this) {
                                this.mEvents.remove(tem);
                            }
                        }
                    }

                }

                setWaitMode(true);

                synchronized (this) {
                    if (this.mShutdown) {
                        continue;
                    }
                    if (this.mChanged) {
                        this.mChanged = false;
                        continue;
                    }
                    this.mChanged = false;

                    scan = this.mEvents.iterator();
                    this.mNextWakeUp = 0L;

                    while (scan.hasNext()) {
                        TimedEventMonitor tem = (TimedEventMonitor) scan.next();

                        if (tem.mNextEvent == 0L) {
                            GA.timer.error("Event with zero timer still in event list");
                            continue;
                        }

                        if (this.mNextWakeUp == 0L) {
                            this.mNextWakeUp = tem.mNextEvent;
                        } else if (tem.mNextEvent < this.mNextWakeUp) {
                            this.mNextWakeUp = tem.mNextEvent;
                        }

                    }

                    try {
                        if (this.mNextWakeUp == 0L) {
                            GA.timer.debug("Sleeping");
                            wait();
                        } else {
                            long waitFor = this.mNextWakeUp
                                    - System.currentTimeMillis();
                            if (waitFor <= 0L) {
                                continue;
                            }
                            GA.timer.debug("Sleeping:" + waitFor);
                            wait(waitFor);
                        }
                    } catch (InterruptedException ex) {
                        GA.timer.error("Caught InterruptedException: Ignoring");
                    }
                }
            }
        } catch (RuntimeException ex) {
            GA.timer.fatal("Dying because of exception " + ex.getMessage(), ex);
        }

        GA.timer.info("Event Timer Stopped");
    }
}
